package turn

import (
	"bufio"
	"container/list"
	"encoding/gob"
	"gitee.com/yysf-xin/dproxy/pkg/tools"
	"golang.org/x/net/proxy"
	"log"
	"net"
	"net/http"
	"strconv"
	"sync"
	"unsafe"
)

var (
	HelloPkg = []byte("dproxy")
)

const (
	ServerName = "Turn"
)

const (
	turnClientTag = "[turn client]"
	turnServerTag = "[turn server]"
)
const (
	PingCommand = iota + 1
	ConnectCommand
)

type Request struct {
	// Protocol version
	Version uint8
	// Requested command
	Command uint8
}

type ConnectMsg struct {
	Id      uint64
	Network string
	Addr    string
}

type ConnectRpy struct {
	Id   uint64
	Code int
}

type PingMsg struct {
}

func (p *PingMsg) SeverDo(codec *ClientCodec, s *Server) {
	_ = codec.Encode(PingCommand, p)
	//tools.Log.Println(turnServerTag, "from client heartbeat")
}
func (p *PingMsg) ClientDo(codec *ClientCodec, c *Client) {
	//tools.Log.Println(turnClientTag, "from server heartbeat")
}

type Call struct {
	Done chan net.Conn
}

func (c *Call) Close() {
	ok := false
	select {
	case _, ok = <-c.Done:
	default:
		ok = true
	}
	if ok {
		close(c.Done)
	}
}

type Client struct {
	GroupId uint64
	Addr    string
	Path    string
	Host    string
	dialer  proxy.Dialer
	codec   *ClientCodec
}

func (c *Client) Close() (err error) {
	if c.codec != nil {
		err = c.codec.Conn.Close()
		c.codec = nil
	}
	return
}

type ClientCodec struct {
	GroupId uint64
	Conn    net.Conn
	Time    int64
	w       *bufio.Writer
	enc     *gob.Encoder
	r       *bufio.Reader
	dec     *gob.Decoder
	el      *list.Element
	rwL     sync.Mutex
}

func (codec *ClientCodec) Encode(command uint8, msg interface{}) (err error) {
	r := Request{
		Version: 1,
		Command: command,
	}
	codec.rwL.Lock()
	if err = codec.enc.Encode(&r); err == nil {
		if err = codec.enc.Encode(msg); err == nil {
			if command == 0 {
				tools.Log.Println("buf", codec.w.Buffered(), codec.w.Available(), unsafe.Pointer(codec.enc))
			}
			err = codec.w.Flush()

			if command == 0 {
				tools.Log.Println("buf", codec.w.Buffered(), codec.w.Available(), unsafe.Pointer(codec.enc))
			}
		}
	}
	if err != nil {
		tools.Log.Println(err)
	}
	codec.rwL.Unlock()
	return
}

type Server struct {
	pending  map[uint64]*Call
	GroupMap map[uint64]*list.List
	Seq      uint64
	mutex    sync.RWMutex
	groupRw  sync.RWMutex
	chooser  GroupChooser
}

func (s *Server) String() string {
	return ServerName
}

type ClientMsgHandler interface {
	ClientDo(codec *ClientCodec, c *Client)
}

type ServerMsgHandler interface {
	SeverDo(codec *ClientCodec, s *Server)
}

func (m *ConnectMsg) ClientDo(codec *ClientCodec, c *Client) {
	go m.goClientDo(codec, c)
}

func (m *ConnectMsg) goClientDo(codec *ClientCodec, c *Client) {
	var (
		conn net.Conn
		err  error
	)
	if c.dialer == nil {
		conn, err = net.Dial("tcp", m.Addr)
	} else {
		conn, err = c.dialer.Dial("tcp", m.Addr)
	}
	if err == nil {
		defer func(conn net.Conn) {
			err = conn.Close()
		}(conn)
		var cr net.Conn
		cr, err = c.DialHTTP(http.MethodConnect, strconv.FormatUint(m.Id, 10))
		if err == nil {
			tools.Log.Println(turnClientTag, "open", m.Addr, err == nil)
			tools.CopyAndWait(conn, cr)
			return
		} else {
			tools.Log.Println(turnClientTag, err)
		}
	} else {
		tools.Log.Println(turnClientTag, "open err", m.Addr, err == nil)
	}
	rpy := ConnectRpy{
		Id:   m.Id,
		Code: 502,
	}
	err = codec.Encode(ConnectCommand, &rpy)

	log.Println("client send error pkg ", rpy)
}
func (h *ConnectRpy) SeverDo(_ *ClientCodec, s *Server) {
	log.Println("server recv client msg ", h)
	s.mutex.RLock()
	call := s.pending[h.Id]
	s.mutex.RUnlock()
	if call != nil {
		call.Close()
	}
	log.Println("call closed")
}

func init() {
	gob.Register(&ConnectMsg{})
	gob.Register(&ConnectRpy{})
	gob.Register(&PingMsg{})
}
